//==============================================================================
// Copyright (c) 2008-2013 Niall Ryan. All Rights Reserved.
//==============================================================================

#include <gtest/gtest.h>

#include "hydra/queues/LockFreeQueue.h"
#include "hydra/queues/LockFreeStampedQueue.h"

using namespace Hydra;

template<typename Queue>
class LockFreeQueueTest : public ::testing::Test
{
protected:
	static const int NUM_THREADS = 4;
	static const int NUM_ITERATIONS = 100000;
	static void PushPop(LockFreeQueueTest* test)
	{
		Queue& queue = test->m_queue;
		for( int i=0; i<NUM_ITERATIONS; ++i )
		{
			queue.push(i%NUM_THREADS);
		}
		for( int i=0; i<NUM_ITERATIONS; ++i )
		{
			int x;
			if( queue.pop(x) )
				++test->m_counters[x];
		}
	}
	static void Push(LockFreeQueueTest* test)
	{
		Queue& queue = test->m_queue;
		for( int i=0; i<NUM_ITERATIONS; ++i )
		{
			queue.push(i);
		}
	}
	static void Pop(LockFreeQueueTest* test)
	{
		Queue& queue = test->m_queue;
		int expected = 0;
		while( true )
		{
			int value;
			if( queue.pop(value) )
			{
				if( value == expected )
					++test->m_counters[0];
				++expected;
				if( expected == NUM_ITERATIONS )
					break;
			}
		}
	}

	Queue m_queue;
	Atomic<int> m_counters[NUM_THREADS];
};

typedef ::testing::Types<LockFreeQueue<int>, LockFreeStampedQueue<int> > LockFreeQueueTestTypes;
TYPED_TEST_CASE(LockFreeQueueTest, LockFreeQueueTestTypes);

TYPED_TEST(LockFreeQueueTest, Stuff)
{
	//
	//single threaded functionality tests
	//
	int value;
	EXPECT_TRUE(!m_queue.pop(value));
	m_queue.push(10);
	EXPECT_TRUE(m_queue.pop(value));
	EXPECT_TRUE(value==10);
	EXPECT_TRUE(!m_queue.pop(value));
	m_queue.push(50);
	m_queue.push(60);
	m_queue.push(70);
	m_queue.push(80);
	EXPECT_TRUE(m_queue.pop(value));
	EXPECT_TRUE(value==50);
	EXPECT_TRUE(m_queue.pop(value));
	EXPECT_TRUE(value==60);
	EXPECT_TRUE(m_queue.pop(value));
	EXPECT_TRUE(value==70);
	EXPECT_TRUE(m_queue.pop(value));
	EXPECT_TRUE(value==80);
	EXPECT_TRUE(!m_queue.pop(value));

	//
	//multithread tests
	//
	Thread threads[NUM_THREADS];
	{
		for( int i=0; i<NUM_THREADS; ++i )
		{
			threads[i] = Thread(&PushPop, this);
			m_counters[i] = 0;
		}
		for( int i=0; i<NUM_THREADS; ++i )
			threads[i].Start();
		for( int i=0; i<NUM_THREADS; ++i )
			threads[i].Join();
		for( int i=0; i<NUM_THREADS; ++i )
			EXPECT_TRUE(m_counters[i]==NUM_ITERATIONS);
		EXPECT_TRUE(!m_queue.pop(value));
	}
	{
		m_counters[0] = 0;
		threads[0] = Thread(&Push, this);
		threads[1] = Thread(&Pop, this);
		threads[0].Start();		threads[1].Start();
		threads[0].Join();		threads[1].Join();
		EXPECT_TRUE(m_counters[0]==NUM_ITERATIONS);
		EXPECT_TRUE(!m_queue.pop(value));
	}
}
